package org.phytodata.actionengine;

import groovy.lang.Binding;
import groovy.lang.GroovyShell;
import groovy.util.GroovyScriptEngine;

import java.io.File;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.pentaho.platform.api.action.IStreamingAction;
import org.pentaho.platform.api.action.IVarArgsAction;
import org.pentaho.platform.api.engine.IPluginManager;
import org.pentaho.platform.engine.core.system.PentahoSystem;
import org.pentaho.platform.plugin.services.pluginmgr.PluginClassLoader;

/**
 * Allows for scheduling of a groovy script
 * @author aaron phillips
 */
public class GroovyEngineAction implements IStreamingAction, IVarArgsAction {

  private String scriptRootDirName;

  private String scriptFileName;
  
  private String scriptText;
  
  private OutputStream scriptOutputStream;
  
  private static Log logger = LogFactory.getLog(GroovyEngineAction.class);

  public void setScriptOutputStream(OutputStream scriptOutputStream) {
    this.scriptOutputStream = scriptOutputStream;
  }

  private Map<String, Object> args = new HashMap<String, Object>();

  private Object scriptResult;

  private boolean useProvidedStreamForSys;

  @Override
  public String getMimeType(String streamPropertyName) {
    return "text/plain";
  }
  
  protected Binding createBinding() {
    Binding binding = new Binding();
    
    for (Map.Entry<String, Object> entry : args.entrySet()) {
      binding.setVariable(entry.getKey(), entry.getValue());
    }
    if(scriptFileName != null) {
      binding.setVariable("scriptFileName", scriptFileName);
    }
    
    if(scriptOutputStream == null) {
      throw new RuntimeException("scriptOutputStream cannot be null");
    }
    
    binding.setVariable("outStream", scriptOutputStream); //another alias for the outputstream
    
    PrintStream printStream = new PrintStream(scriptOutputStream);
    binding.setVariable("printStream", printStream);
    
    if(useProvidedStreamForSys) {
      binding.setVariable("out", printStream);
      //Temporarily swap out the std out and err streams with the runtime one.
      System.setOut(printStream);
      System.setErr(printStream);
    }
    
    Log log = LogFactory.getLog(GroovyEngineAction.class);
    binding.setVariable("log", log);
    
    //adjust the logging level
    Logger rootLogger = Logger.getRootLogger();
    Logger pkgLogger = rootLogger.getLoggerRepository().getLogger(GroovyEngineAction.class.getName());
    pkgLogger.setLevel(Level.DEBUG);
    
    //
    // Convenience bindings.. do these belong here?
    //
    binding.setVariable("actionFactory", new ActionFactory());  //TODO make ActionFactory a singleton
    
    return binding;
  }

  @Override
  public void execute() throws Exception {
    final PrintStream origStdOut = System.out;
    final PrintStream origStdErr = System.err;
    
    try {
    
    if(scriptText != null) {
      //better for running scripts dynamically
      executeInShell();
    } else {
      //better for running script files
      executeInEngine();
    }
    }
    finally {
      if(useProvidedStreamForSys) {
        System.setOut(origStdOut);
        System.setErr(origStdErr);
      }
    }
  }
  
  public void executeInShell() {
    Binding binding = createBinding();
    scriptResult = new GroovyShell(binding).evaluate(scriptText);
  }
    
  public void executeInEngine() throws Exception {
    logger.error("CWD: "+new File(".").getAbsolutePath());
    
    File scriptRoot = new File(scriptRootDirName);
    if(!scriptRoot.exists() || !scriptRoot.isDirectory() || !scriptRoot.canRead()) {
      throw new RuntimeException("Invalid script root dir \""+scriptRootDirName+"\"");
    }
    
    logger.error("script root: "+scriptRoot.getAbsolutePath());
    
    String[] roots = new String[] { scriptRoot.getAbsolutePath() };
    
    
    
    //start ILLEGAL stuff. IActions should not know about the BIPlatform
    //
    IPluginManager pluginManager = PentahoSystem.get(IPluginManager.class);
    PluginClassLoader thisPluginLoader = (PluginClassLoader)this.getClass().getClassLoader();
    PluginClassLoader loaderToUse = thisPluginLoader;
    ClassLoader cdaLoader = pluginManager.getClassLoader("Pentaho Community Data Access");
    if(cdaLoader != null) {
      loaderToUse = new PluginClassLoader(thisPluginLoader.getPluginDir(), cdaLoader);
    }
    //
    //end ILLEGAL stuff
    
    
    GroovyScriptEngine gse = new GroovyScriptEngine(roots, loaderToUse);

    Binding binding = createBinding();

    //
    // Execute the script
    //
    scriptResult = gse.run(scriptFileName, binding);
    
    //TODO: set outputs of script binding here?
  }

  public void setScriptRootDirName(String scriptRootDirName) {
    this.scriptRootDirName = scriptRootDirName;
  }

  public void setScriptFileName(String scriptFileName) {
    this.scriptFileName = scriptFileName;
  }

  public String getScriptText() {
    return scriptText;
  }

  public void setScriptText(String scriptText) {
    this.scriptText = scriptText;
  }

  public Object getScriptResult() {
    return scriptResult;
  }

  public boolean isUseProvidedStreamForSys() {
    return useProvidedStreamForSys;
  }

  public void setUseProvidedStreamForSys(boolean useProvidedStreamForSys) {
    this.useProvidedStreamForSys = useProvidedStreamForSys;
  }

  @Override
  public void setVarArgs(Map<String, Object> args) {
    this.args = args;
  }

}
